#include "GLSLShader.h"
#include "../OpenGLSupport.h"
#include "../../../HObject.h"
#include "../../../Util/StringUtil.h"

#include <string>
#include <map>

using namespace std;

namespace helix
{

	GLSLShader::GLSLShader(const char* vertexShaderSource, const char* fragmentShaderSource)
	{
		char logMessage[256];

		// Create Shader And Program Objects
		program = glCreateProgramObjectARB();
		vertexShader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
		fragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);

		//Compile the shaders and attach them to the program.		
		compileShader(vertexShader,vertexShaderSource);
		compileShader(fragmentShader,fragmentShaderSource);

		// Link The Program Object
		glLinkProgramARB(program);
		logInfoLog(program);
	}

	GLSLShader::~GLSLShader()
	{
		glDeleteObjectARB(vertexShader);
		glDeleteObjectARB(fragmentShader);
		glDeleteObjectARB(program);
	}

	void GLSLShader::useShader(bool use)
	{
		if(use)
			glUseProgramObjectARB(program);
		else
			glUseProgramObjectARB(0);
	}

	void GLSLShader::compileShader(GLhandleARB shader, const char* shaderSource)
	{
		string shaderSrc(shaderSource);
		if(!shaderSrc.empty())
		{
			//remove extra new line characters if they exist.
			StringUtil::replace(&shaderSrc,"\r","");
			const char *formatedSrc = shaderSrc.c_str();
			glShaderSourceARB(shader, 1, &formatedSrc, NULL);
			logInfoLog(shader);

			glCompileShaderARB(shader);
			logInfoLog(shader);

			glAttachObjectARB(program,shader);
		}
		else
		{
			Log().Println("GLSLShader::compileShader: shader did not have source to compile.  Check to make sure your file exists.");
		}
	}

	void GLSLShader::logInfoLog(GLhandleARB object)
	{
		GLcharARB logResult[INFO_LOG_SIZE];
		GLsizei *length = 0;

		glGetInfoLogARB(object, INFO_LOG_SIZE, length, logResult);

		string result(logResult);
		if(!result.empty() && (result.find("Error")!=string::npos || result.find("error")!=string::npos) )
		{
			Log().Error("GLSLShader::infoLog: %s",logResult);
		}
		else if(!result.empty() )
		{
			Log().Println("GLSLShader::infoLog: %s",logResult);
		}

	}

	GLint GLSLShader::getVariableLocation(unsigned int type, const char *name)
	{
		string variableName(name);

		map<string,GLint> *varMap = NULL;
		if(type == UNIFORM_VARIABLE)
		{
			varMap = &uniforms;
		}
		else if(type == ATTRIBUTE_VARIABLE)
		{
			varMap = &attributes;
		}

		if(varMap==NULL)
		{
			Log().Error("GLSLShader::getVariableLocation:: could not get the internal variable map.");
			return -1;
		}

		map<string,GLint>::iterator itr = varMap->find(variableName);
		if(itr == varMap->end())
		{
			Log().Println("GLSLShader::getVariableLocation:: Variable location for '%s' not initialized.  Retreiving from shader.",name);
			GLint location = glGetUniformLocationARB(program,name);
			(*varMap)[variableName] = location;
		}

		GLint location = (*varMap)[variableName];

		return location;
	}

	void GLSLShader::assignUniformF(const char *name, unsigned int count, float *value)
	{
		GLint location = getVariableLocation(UNIFORM_VARIABLE,name);
		if(location == -1)
		{
			Log().Error("The uniform variable '%s' does not exist in the shader.",name);
			return;
		}

		switch(count)
		{
		case 1:
			glUniform1fvARB(location,1,value);
			break;
		case 2:
			glUniform2fvARB(location,1,value);
			break;
		case 3:
			glUniform3fvARB(location,1,value);
			break;
		case 4:
			glUniform4fvARB(location,1,value);
			break;
		default:
			Log().Error("The uniform variable '%s' could not be set because the count (%d) was not between 1 and 4.",name,count);
			break;
		}
	}

	void GLSLShader::assignUniformI(const char *name, unsigned int count, int *value)
	{
		GLint location = getVariableLocation(UNIFORM_VARIABLE,name);
		if(location == -1)
		{
			Log().Error("The uniform variable '%s' does not exist in the shader.",name);
			return;
		}

		switch(count)
		{
		case 1:
			glUniform1ivARB(location,1,value);
			break;
		case 2:
			glUniform2ivARB(location,1,value);
			break;
		case 3:
			glUniform3ivARB(location,1,value);
			break;
		case 4:
			glUniform4ivARB(location,1,value);
			break;
		default:
			Log().Error("The uniform variable '%s' could not be set because the count (%d) was not between 1 and 4.",name,count);
			break;
		}
	}

	void GLSLShader::assignTextureSampler(const char *name, int textureUnit)
	{
		assignUniformI(name, 1, &textureUnit);
	}
}